package ws.jdbc.concurrent.driver.sqlserver.cursor.shared;

import ws.jdbc.concurrent.driver.sqlserver.Messages;
import ws.jdbc.concurrent.driver.sqlserver.tds.ITDSResultSet;
import ws.jdbc.concurrent.driver.sqlserver.ResultSetCursor;
import java.sql.Date;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import ws.jdbc.concurrent.driver.sqlserver.CResultSet;
import ws.jdbc.concurrent.driver.sqlserver.ResultSetScroll;
import ws.jdbc.concurrent.driver.sqlserver.tds.TDSMessage;
import static ws.jdbc.concurrent.driver.sqlserver.tds.rpc.StoredProcedures.*;

/**
 * This class provides the functionality of a result set cursor with shared cache.
 * 
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public abstract class CursorSharedCache extends ResultSetCursor {

  public CursorSharedCache(final TDSMessage message, final CResultSet crs, final ReentrantLock lock) throws SQLException {
    super(message, crs, lock);
    lock.lock();
    try {
      if (tdsResultSet.isEmpty()) {
        tdsResultSet.addRows(message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_NEXT, currentRow, fetchSize));
        lastRow.set(tdsResultSet.rowCount());
      }
    } finally {
      lock.unlock();
    }
    cacheBegining = 1;
    cacheEnd = cacheBegining + lastRow.get();
  }

  @Override
  public void refreshRow() throws SQLException {
    lock.lock();
    try {
      ITDSResultSet tmprs = message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_REFRESH, 0, 0);
      tdsResultSet.updateRow(rsIndex, tmprs.getRow(rsIndex));
    } finally {
      lock.unlock();
    }
  }

  @Override
  public void afterLast() throws SQLException {
    currentRow = (lastRow.get() + 1);
    rsIndex = currentRow - 1;
  }

  @Override
  public void beforeFirst() throws SQLException {
    currentRow = ROW_BEFORE;
    rsIndex = -1;
  }

  @Override
  public int getInt(int columnIndex) throws SQLException {
    lastColumnIndex = columnIndex - 1;
    final Integer ret;
    lock.lock();
    try {
      ret = tdsResultSet.getInt(rsIndex, lastColumnIndex);
    } finally {
      lock.unlock();
    }
    return ret;
  }

  @Override
  public String getString(int columnIndex) throws SQLException {
    lastColumnIndex = columnIndex - 1;
    final String ret;
    lock.lock();
    try {
      ret = tdsResultSet.getString(rsIndex, lastColumnIndex);
    } finally {
      lock.unlock();
    }
    return ret;
  }

  @Override
  public double getDouble(int columnIndex) throws SQLException {
    lastColumnIndex = columnIndex - 1;
    final Double ret;
    lock.lock();
    try {
      ret = tdsResultSet.getDouble(rsIndex, lastColumnIndex);
    } finally {
      lock.unlock();
    }
    return ret;
  }

  @Override
  public Date getDate(int columnIndex) throws SQLException {
    lastColumnIndex = columnIndex - 1;
    final Date ret;
    lock.lock();
    try {
      ret = tdsResultSet.getDate(rsIndex, lastColumnIndex);
    } finally {
      lock.unlock();
    }
    return ret;
  }

  @Override
  public void updateRow() throws SQLException {
    lock.lock();
    try {
      message.cursorUpdate(tdsResultSet.getCursor(), rsIndex + 1, updateValues);
      /* Update cache with new values. */
      tdsResultSet.updateRow(rsIndex, updateValues);  
    } finally {
      lock.unlock();
    }
    cancelRowUpdates();
  }

  @Override
  public void insertRow() throws SQLException {
    if (currentRow != ROW_INSERT) {
      cancelRowUpdates();
      throw new SQLException(Messages.msg_is_not_on_insert_row);
    }
    lock.lock();
    try {
      message.cursorInsert(tdsResultSet.getCursor(), updateValues);
      /* Insert new values. */
      tdsResultSet.insertValues(updateValues);
      lastRow.incrementAndGet();
    } finally {
      lock.unlock();
    }
    cancelRowUpdates();
  }

  /**
   * Scroll the result set.
   *
   * @param impl result set scroll implementation.
   *
   * @return {@code true} if the result set is in a row, otherwise {@code false}.
   *
   * @throws SQLException if an i/o error occurs while reading the result set from the input stream.
   */
  @Override
  protected boolean scroll(final ResultSetScroll impl) throws SQLException {
    impl.calcCurrentRow();
    if (currentRow < 1) {
      currentRow = ROW_BEFORE;
      rsIndex = -1;
      return false;
    }
    if (currentRow > lastRow.get()) {
      currentRow = (lastRow.get() + 1);
      rsIndex = currentRow - 1;
      return false;
    }
    calcRSIndex();
    return true;
  }
}